跳到主要内容

SpringSecurity 使用自带的 formLogin

表单登陆配置​

在 WebSecurityConfig 中配置表章登录信息:

//配置安全拦截机制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/r/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin() (1)
.loginPage("/login‐view") (2)
.loginProcessingUrl("/login") (3)
.successForwardUrl("/login‐success") (4)
.permitAll();
}

(1)允许表单登录 (2)指定我们自己的登录页,spring security以重定向方式跳转到/login-view (3)指定登录处理的URL,也就是用户名、密码表单提交的目的路径 (4)指定登录成功后的跳转URL (5)我们必须允许所有用户访问我们的登录页(例如为验证的用户),这个 formLogin().permitAll() 方法允许任意用户访问基于表单登录的所有的URL。

会话控制​

我们可以通过以下选项准确控制会话何时创建以及 Spring Security 如何与之交互:

通过以下配置方式对该选项进行配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
}

默认情况下,Spring Security 会为每个登录成功的用户会新建一个 Session,就是 ifRequired。

若选用 never,则指示 Spring Security 对登录成功的用户不创建 Session了,但若你的应用程序在某地方新建了 session,那么 Spring Security 会用它的。

若使用 stateless,则说明 Spring Security 对登录成功的用户不会创建 Session 了,你的应用程序也不会允许新建 session。并且它会暗示不使用 cookie,所以每个请求都需要重新进行身份验证。这种无状态架构适用于 REST API 及其无状态认证机制。

会话超时​

可以再 servlet 容器中设置 Session 的超时时间,如下设置 Session 有效期为 3600s;

spring boot 配置文件:

server.servlet.session.timeout=3600s

session 超时之后,可以通过 Spring Security 设置跳转的路径。

http.sessionManagement()
.expiredUrl("/login‐view?error=EXPIRED_SESSION")
.invalidSessionUrl("/login‐view?error=INVALID_SESSION");

expired 指 session 过期,invalidSession 指传入的 session_id 无效。

我们可以使用 httpOnly 和 secure 标签来保护我们的会话 cookie:

  • httpOnly:如果为 true,那么浏览器脚本将无法访问 cookie
  • secure:如果为 true,则 cookie 将仅通过 HTTPS 连接发送

spring boot 配置文件:

server.servlet.session.cookie.http‐only=true
server.servlet.session.cookie.secure=true

Session、Cookie 登陆认证​

就是认证是否为合法用户,简单的说是登录。一般为匹对用户名和密码,即认证成功。

在 Spring Security 认证中,只要解决如下几个问题:

  • 哪个类表示用户?
  • 哪个属性表示用户名?
  • 哪个属性表示密码?
  • 怎么通过用户名取到对应的用户?
  • 密码的验证方式是什么?

所有的自定义行为都是围绕这几个问题展开的

认证的执行流程就是

  1. 它会拿到用户输入的用户名密码;
  2. 根据用户名通过 UserDetailsService 的 loadUserByUsername(username) 方法获得一个用户对象;
  3. 获得一个 UserDetails 对象,获得内部的成员属性 password;
  4. 通过 PasswordEncoder 的 matchs(s1, s2) 方法对比用户的输入的密码和第3步的密码;
  5. 匹配成功;

获取用户名和密码​

Spring Boot 整合 Spring Security(前后端分离时的json登录方式,解决获取不到用户名密码问题) springboot+security整合2

默认的账户名和密码的参数名分别是 username、password 可以自定义账户和密码的参数名

http
.formLogin()
.usernameParameter("my_username")
.passwordParameter("my_password")

如果是自己验证用户名密码的话,Spring Security 仅仅支持传统的 form 表单方式(form-data)登录。这是一个比较大的坑点。现在都流行使用前后端分离,前端发送的是 json 格式数据。所以需要自己定制 UsernamePasswordAuthenticationFilter 这个类

获取用户名密码是在 UsernamePasswordAuthenticationFilter 这个类里面的 attemptAuthentication 方法,如下

public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}

String username = obtainUsername(request);
String password = obtainPassword(request);

if (username == null) {
username = "";
}

if (password == null) {
password = "";
}

username = username.trim();

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);

// Allow subclasses to set the "details" property
setDetails(request, authRequest);

return this.getAuthenticationManager().authenticate(authRequest);
}

再进一步可以看到获取用户名密码的方法

// 这个 passwordParameter 为 password
// 同理 usernameParameter 为 username
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}

所以如果要从 JSON 里取得密码和用户名,需要继承这个 UsernamePasswordAuthenticationFilter 类,重写 attemptAuthentication 方法(例如添加验证码之类的操作也是在这里入手)

例如这里增加一个验证码操作

public class MyUsernamePasswordAuthentication extends UsernamePasswordAuthenticationFilter{

private Logger log = LoggerFactory.getLogger(this.getClass());

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
//我们可以在这里进行额外的验证,如果验证失败抛出继承AuthenticationException的自定义错误。
log.info("在这里进行验证码判断");
//只要最终的验证是账号密码形式就无需修改后续过程
return super.attemptAuthentication(request, response);
}

@Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
// TODO Auto-generated method stub
super.setAuthenticationManager(authenticationManager);
}
}

将自定义登录配置到 Security 中

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf() // 跨站
.disable() // 关闭跨站检测
// 自定义鉴权过程,无需下面设置
// 验证策略
.authorizeRequests()
// 无需验证路径
.antMatchers("/public/**").permitAll()
.antMatchers("/user/**").permitAll()
// 放行登录
.antMatchers("/login").permitAll()
.antMatchers(HttpMethod.GET, "/user").hasAuthority("getAllUser") // 拥有权限才可访问
// 拥有任一权限即可访问
.antMatchers(HttpMethod.GET, "/user").hasAnyAuthority("1","2")
// 角色类似,hasRole(),hasAnyRole()
.anyRequest().authenticated()
.and()
// 自定义异常处理
.exceptionHandling()
.authenticationEntryPoint(myAuthenticationEntryPoint) // 未登录处理
.accessDeniedHandler(myAccessDeniedHandler)//权限不足处理
.and()
// 加入自定义登录校验
.addFilterBefore(myUsernamePasswordAuthentication(),UsernamePasswordAuthenticationFilter.class)
// 默认放在内存中
.rememberMe()
.rememberMeServices(rememberMeServices())
.key("INTERNAL_SECRET_KEY")
// 重写 usernamepasswordauthenticationFilter 后,下面的formLogin()设置将失效,需要手动设置到个性化过滤器中
// .and()
// .formLogin()
// .loginPage("/public/unlogin") //未登录跳转页面,设置了authenticationentrypoint后无需设置未登录跳转面
// .loginProcessingUrl("/public/login")//登录api
// .successForwardUrl("/success")
// .failureForwardUrl("/failed")
// .usernameParameter("id")
// .passwordParameter("password")
// .failureHandler(myAuthFailedHandle) //登录失败处理
// .successHandler(myAuthSuccessHandle)//登录成功处理
// .usernameParameter("id")
.and()
.logout()//自定义登出
.logoutUrl("/public/logout")
.logoutSuccessUrl("public/logoutSuccess")
.logoutSuccessHandler(myLogoutSuccessHandle);
}

// 然后再编写Bean,代码如下:
@Bean
public MyUsernamePasswordAuthentication myUsernamePasswordAuthentication(){
MyUsernamePasswordAuthentication myUsernamePasswordAuthentication = new MyUsernamePasswordAuthentication();
myUsernamePasswordAuthentication.setAuthenticationFailureHandler(myAuthFailedHandle); //设置登录失败处理类
myUsernamePasswordAuthentication.setAuthenticationSuccessHandler(myAuthSuccessHandle);//设置登录成功处理类
myUsernamePasswordAuthentication.setFilterProcessesUrl("/public/login");
myUsernamePasswordAuthentication.setRememberMeServices(rememberMeServices()); //设置记住我
myUsernamePasswordAuthentication.setUsernameParameter("id");
myUsernamePasswordAuthentication.setPasswordParameter("password");
return myUsernamePasswordAuthentication;
}

UserDetails​

参考资料 Spring Security自定义用户认证

这个接口就是下面的 UserDetailsService 的返回值,虽然 Spring Security 自带的实现类 org.springframework.security.core.userdetails.User 已经够强大了,但是还有有必要去了解这个接口,以便后续自定义

该对象也是一个接口,包含一些用于描述用户信息的方法,源码如下:

public interface UserDetails extends Serializable {

Collection<? extends GrantedAuthority> getAuthorities();

String getPassword();

String getUsername();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();
}
  • getAuthorities 获取用户包含的权限,返回权限集合,权限是一个继承了 GrantedAuthority 的对象;
  • getPassword 和 getUsername 用于获取密码和用户名;
  • isAccountNonExpired 方法返回 boolean 类型,用于判断账户是否未过期,未过期返回 true 反之返回 false;
  • isAccountNonLocked 方法用于判断账户是否未锁定;
  • isCredentialsNonExpired 用于判断用户凭证是否没过期,即密码是否未过期;
  • isEnabled 方法用于判断用户是否可用。

UserDetailsService​

怎么通过用户名取到对应的用户?

只需要去实现这个 UserDetailsService 接口,里面有个 loadUserByUsername 方法就是用来找到用户的

public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

在这个 loadUserByUsername(String username) 里面实现登陆逻辑,如果未找到用户可以抛出一个 UsernameNotFoundException 异常,返回值是一个 UserDetails 接口的实现类,默认会使用 Spring Security 定义的 User 类

实现 UserDetailsService 接口例

@Service
public class UserDetailServiceImpl implements UserDetailsService {

// 懒得连接数据库,这里直接使用 Map 替代
private static final Map<String, String> dates;

static {
dates = new HashMap<>();
// username, password
dates.put("admin", "admin");
}

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String rawPassword = dates.get(username);
// 这里模拟去数据库查询
if (rawPassword == null) {
throw new UsernameNotFoundException("用户名不存在");
}

// 匹配成功则用其生成加密后的密文传入 UserDetails
String password = passwordEncoder.encode(rawPassword);
// 这个 commaSeparatedStringToAuthorityList 就是把输入的字符串根据逗号分割成 List<GrantedAuthority>
return new User(username, password, AuthorityUtils
.commaSeparatedStringToAuthorityList("admin,normal"));
}
}

这里 User 对象返回值的第三个参数实际上就是权限列表 Collection<? extends GrantedAuthority> authorities

这里通过 AuthorityUtils.commaSeparatedStringToAuthorityList() 这个工具类将权限转换成 GrantedAuthority 集合(使用 , 分割不同权限)

比对权限是否存在的方式

@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
// 获取主体
Object principal = authentication.getPrincipal();
log.info(request.getRequestURI());
// 判断主体是否属于 UserDetails
if (principal instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principal;
// 获取权限列表
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
// 判断请求的 URI 是否在权限里
return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
}
return false;
}

PasswordEncoder​

loadUserByUsername 这个方法可以看到只有一个 username 参数,那密码在哪里比较呢? 密码的比较使用的是 PasswordEncoder;它也是一个接口

public interface PasswordEncoder {
// 加密密码
String encode(CharSequence rawPassword);
// 对密码进行比对
boolean matches(CharSequence rawPassword, String encodedPassword);
// 对已经加密的密码再次加密
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}

不过一般使用自带的实现类 BCryptPasswordEncoder 使用例

@Test
void testPasswordEncoder() {
PasswordEncoder pw = new BCryptPasswordEncoder();
// 加密密码
// 注意 这个 BCrypt加密算法每次加密得到的密文都是不一样的,所以就算是一样的密码两次加密都不会相同
String encode = pw.encode("12345678");
log.info(encode);
// 比对密码
boolean matches = pw.matches("12345678", encode);
log.info(String.valueOf(matches));
}

/** 输出如下
* $2a$10$28sxxWLV85qKIIGK4mR9YuM/JjBGotUnaX8WROHjDV1IcLsmXIhOG
* true
*/

登陆成功处理器​

就是去实现 AuthenticationSuccessHandler 接口

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
RespBean ok = RespBean.ok("登录成功!", authentication.getPrincipal());
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(ok));
out.flush();
out.close();
}
}

然后再在配置里使用自定义的处理器就行了

http
...
.successHandler(new MyAuthenticationSuccessHandler())

登陆失败处理器​

去实现 AuthenticationFailureHandler 接口

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
RespBean error = RespBean.error("登录失败");
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(error));
out.flush();
out.close();
}
}

然后再在配置里使用自定义的处理器

http
...
.failureHandler(new MyAuthenticationFailureHandler())

自定义 403 处理​

去实现 AccessDeniedHandler 接口

public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
RespBean error = RespBean.error("权限不足,访问失败");
response.setStatus(403);
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(error));
out.flush();
out.close();
}
}

除了上面那种高度自定义的写法还可以像这样直接调用方法

@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(403, "权限不足,访问失败");
}

注册这个 Handle

http
.exceptionHandling()
// 这个 Http403ForbiddenEntryPoint 是自带的实现类
.authenticationEntryPoint(new Http403ForbiddenEntryPoint()) // 未登陆的请求处理
.accessDeniedHandler(new MyAccessDeniedHandler()) // 未授权的请求处理

如何判断用户状态的?​

用户登陆后 Spring Security 是怎么知道用户已经登陆过了呢?答案就是 Cookie 和 Session,用户登陆后服务端会返回一个 Cookie

JSESSIONID=41AEFF280BB9BF67CC23E9B9C740B075;

因为每次请求都会带上 Cookie,所以 Spring Security 能基于此完成对用户权限的鉴别

注意:如果使用的是 Ajax 跨域请求,需要配置一下,否则默认是不携带 Cookie 的

这里只讲 axios 的设置,后端的设置参看跨域请求那一篇文章

// 因为默认 Spring Security 是通过 Cookie 和 Session 来验证身份的,所以需要配置携带 Cookie
axios.interceptors.request.use(config => {
config.withCredentials = true;
return config;
});

退出​

Spring security 默认实现了 logout 退出,访问 /logout,果然不出所料,退出功能 Spring 也替我们做好了。

点击 “Log Out” 退出 成功。

退出后访问其它 url 判断是否成功退出。

这里也可以自定义退出成功的页面:

在 WebSecurityConfig 的 protected void configure(HttpSecurity http) 中配置:

.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login‐view?logout");

当退出操作出发时,将发生:

  • 使 HTTP Session 无效
  • 清除 SecurityContextHolder
  • 跳转到 /login-view?logout

但是,类似于配置登录功能,咱们可以进一步自定义退出功能:

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
//...
.and()
.logout() (1)
.logoutUrl("/logout") (2)
.logoutSuccessUrl("/login‐view?logout") (3)
.logoutSuccessHandler(logoutSuccessHandler) (4)
.addLogoutHandler(logoutHandler) (5)
.invalidateHttpSession(true); (6)
}

(1)提供系统退出支持,使用 WebSecurityConfigurerAdapter 会自动被应用 (2)设置触发退出操作的 URL (默认是 /logout ). (3)退出之后跳转的 URL。默认是 /login?logout。 (4)定制的 LogoutSuccessHandler ,用于实现用户退出成功时的处理。如果指定了这个选项那么 logoutSuccessUrl() 的设置会被忽略。 (5)添加一个 LogoutHandler ,用于实现用户退出时的清理工作默认 SecurityContextLogoutHandler 会被添加为最后一个 LogoutHandler 。 (6)指定是否在退出时让 HttpSession 无效。 默认设置为 true。

注意:如果让 logout 在 GET 请求下生效,必须关闭防止 CSRF 攻击 csrf().disable()。如果开启了 CSRF,必须使用 post 方式请求 /logout

logoutHandler​

一般来说, LogoutHandler 的实现类被用来执行必要的清理,因而他们不应该抛出异常。

下面是 Spring Security 提供的一些实现:

  • PersistentTokenBasedRememberMeServices 基于持久化 token 的 RememberMe 功能的相关清理
  • TokenBasedRememberMeService 基于 token 的 RememberMe 功能的相关清理
  • CookieClearingLogoutHandler 退出时 Cookie 的相关清理
  • CsrfLogoutHandler 负责在退出时移除 csrfToken
  • SecurityContextLogoutHandler 退出时 SecurityContext 的相关清理

链式 API 提供了调用相应的 LogoutHandler 实现的快捷方式,比如 deleteCookies()。

自定义登陆页​

默认 Spring Security 有一个自带的登陆界面,其账户名是 user 密码会在控制台打印出来,用户访问 /login 路径会跳转到这个登陆页,虽然集成了登陆页挺好的,但是一般都是需要各种客制化,所以还是需要掌握如何自定义登陆

前端分离登陆​

参考资料 Spring Security登录使用JSON格式数据 参考资料 spring security简单教程以及实现完全前后端分离

现在大部分前后端分离的 Web 程序,尤其是前端普遍使用 Ajax 请求时,Spring Security 自带的登录系统就有一些不满足需求了。

因为 Spring Security 有自己默认的登录页,自己默认的登录控制器。而登录成功或失败,都会返回一个 302 跳转。登录成功跳转到主页,失败跳转到登录页。如果未认证直接访问也会跳转到登录页。但是如果前端使用 Ajax 请求,Ajax 是无法处理 302 请求的。前后端分离 Web 中,规范是使用 Json 交互。我们希望登录成功或者失败都会返回一个 Json。

注:登录接口和登录页面的区别,登录页面就是浏览器展示出来的页面;登录接口则是提交登录数据的地方,就是登录页面里边的 form 表单的 action 属性对应的值。

在 Spring Security 中,如果我们不做任何配置,默认的登录页面和登录接口的地址都是 /login,也就是说,默认会存在如下两个请求:

GET http://localhost:8080/login
POST http://localhost:8080/login
  • loginProcessingUrl:这个表示配置处理登录请求的接口地址,例如你是表单登录,那么 form 表单中 action 的值就是这里填的值。
  • loginPage:这个表示登录页的地址,例如当你访问一个需要登录后才能访问的资源时,系统就会自动给你通过重定向跳转到这个页面上来。(但是这个是前后端不分家时才使用的东西)

可以自定义 AuthenticationSuccessHandler、AuthenticationFailureHandler、AccessDeniedHandler 来返回不同的 JSON

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Autowired
private UserDetailServiceImpl userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义的 UserDetailService
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 验证策略
.authorizeRequests()
// 放行登录
.antMatchers(HttpMethod.POST,"/doLogin").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/doLogin")
// 设置登陆成功后的处理(这个 MyAuthenticationSuccessHandler 是之前自定义的处理器,下面的同理)
.successHandler(new MyAuthenticationSuccessHandler())
// 设置登陆失败后的处理
.failureHandler(new MyAuthenticationFailureHandler())
.and()
.csrf()// 要关掉这个 csrf
.disable()
.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint()) // 未登陆直接请求资源的处理(这里设置为返回 403)
.accessDeniedHandler(new MyAccessDeniedHandler())
.and().cors();

}
}

注意这里的坑点!!csrf().disable() 这里的 csrf 一定要关掉,否则会一直显示 “权限不足,访问失败”。在 Security 的默认拦截器里,默认会开启 CSRF 处理,判断请求是否携带了 token,如果没有就拒绝访问。

不要将 CORS(跨站资源共享) 和 CSRF(跨站请求伪造)弄混

  • CORS(跨站资源共享) 是局部打破同源策略的限制,使在一定规则下 HTTP 请求可以突破浏览器限制,实现跨站访问。
  • CSRF 是一种网络攻击方式,也可以说是一种安全漏洞,这种安全漏洞在 web 开发中广泛存在。

退出登陆​

http
.logout()
// 默认是 /logout
.logoutUrl("/doLogout")
//退出成功,返回json
.logoutSuccessHandler((request,response,authentication) -> {
RespBean ok = RespBean.ok("退出成功!", authentication.getPrincipal());
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(new ObjectMapper().writeValueAsString(ok));
out.flush();
out.close();
}).permitAll();

配置测试环境​

使用 Vue 搭建一个测试环境

<body>
<div id="app">
<div>
<span>username:</span>
<input type="text" v-model='username'>
</div>
<div>
<span>password:</span>
<input type="text" v-model='password'>
</div>
<button @click='sub'>提交</button>
<br/>
<br/>
<!-- 登陆成功后再访问这个 api -->
<button @click="sayHello()">测试访问需要权限的 api</button>
<br/>
<br/>
<button @click="logout()">退出登陆</button>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.bootcss.com/qs/6.5.1/qs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const qs = Qs // 引入QS库把请求参数转成表单形式
let app = new Vue({
el: '#app',
created() {
axios.defaults.baseURL = 'http://127.0.0.1:8080/'
// 因为默认 Spring Security 是通过 Cookie 和 Session 来验证身份的,所以需要配置携带 Cookie
axios.interceptors.request.use(config => {
config.withCredentials = true;
return config;
});
},
data: {
password: 'admin',
username: 'admin'
},
methods: {
sub() {
axios.post('/doLogin', qs.stringify({
username: this.username,
password: this.password
}), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then((result) => {
console.log(result.data);
}).catch((err) => {
console.log(err);
});
},
sayHello() {
axios.get('/hello')
.then((result) => {
console.log(result.data);
}).catch((err) => {
console.log(err);
});
},
logout() {
axios.post('/doLogout')
.then((result) => {
console.log(result.data);
}).catch((err) => {
console.log(err);
});
}
},
})
</script>
</body>